iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
Modern Web

從零開始:全端新手的困境與成長系列 第 25

Day25 登入功能初體驗,JWT 的身份驗證流程!(上)

  • 分享至 

  • xImage
  •  

登入功能看起來簡單,但其實背後涉及不少技術細節,尤其是在設計和實作的過程中,許多新手開發者會面臨很多問題。比如,「要怎麼確保資料安全?」或者「如何區分不同的使用者權限?」這些都可能感到頭痛。今天這篇文章,我們就來探討一下如何透過 JWT 來建立一個完整的身份驗證流程,讓你輕鬆應對這些挑戰。

https://ithelp.ithome.com.tw/upload/images/20241003/20168326l27uMXtIgM.png

文章大綱:

  1. 推開迷霧:破解身份驗證
  2. 安全高效的 JWT
  3. Node.js + Express 中實作 JWT
  4. 使用強密鑰來加強 JWT 安全性
  5. 驗證 JWT 保護後端資源
  6. 使用 Postman 測試後端 JWT 身份驗證流程

1. 推開迷霧:破解身份驗證

當第一次接觸身份驗證時,可能會覺得困難重重。像我當初一樣,曾經一度想著:「該不會要直接把帳號密碼存到資料庫吧?」但這種做法既危險又不實際,資料有很大可能會被駭客竊取。隨著專案的進展,開始接觸 JWT 時,又會擔心 Token 是否安全、如何管理不同的使用者權限,以及該怎麼確保登入狀態。

不過,當掌握了 JWT 的概念並實作完成身份驗證功能後,真的會感到成就滿滿!從這個過程中,你會學到許多關於後端安全性和用戶管理的知識,還會幫助你在開發技能上更上一層樓。

內容接下來很多,走囉!


2. 安全高效的 JWT

現在來介紹登入功能的核心工具 —— JWT(JSON Web Token)!JWT 讓你在身份驗證的流程中保持簡單而安全,伺服器可以通過這個 Token 確認用戶身份,從而省去每次查詢資料庫的麻煩。

JWT 是什麼?如何運作?

JWT 是一個包含三部分的 Token,分別是 Header、Payload 和 Signature。

  • Header:包含 Token 的加密算法類型(如 HS256)。
  • Payload:包含用戶的資訊(如 userIdroles),伺服器可以通過這部分來確認用戶身份。
  • Signature:確保 Token 的真實性,防止被竄改。

JWT 的好處在於,它可以讓伺服器認識到用戶的身份而不必每次都查詢資料庫,節省效能並確保安全性。

使用 JWT 官網工具進行解碼

想要看看一個實際的 JWT 長什麼樣?你可以前往 JWT 官方網站,那裡有個工具讓你可以解密和測試 JWT!你只需要將一個 Token 貼到 encoded 區域,就能解析出 Header、Payload 和 Signature。

你會看到 Token 的三個部分清楚地顯示出來,了解每一個部分的內容和作用,這對我來說是非常好用的工具,具體步驟如下:

  1. 貼上你的 JWT Token 到 Encoded 區域。
  2. 看著 Decoded 區域的 Header、Payload 和 Signature 逐一解析出來,了解 Token 的內容結構。
  3. 檢查 Signature 部分,確認這個 Token 的真實性。

https://ithelp.ithome.com.tw/upload/images/20241003/20168326SEKZ9VI9Oc.png


3. Node.js + Express 中實作 JWT

接下來我們將一步步教你如何在 Node.js + Express 中實作 JWT,生成和驗證 Token。

步驟 1:安裝 jsonwebtokendotenv

我們首先需要安裝 jsonwebtoken 來生成 JWT,同時使用 dotenv 管理環境變數,這樣可以避免將密鑰直接寫死在程式碼裡。

npm install jsonwebtoken dotenv

步驟 2:設置環境變數

為了確保 JWT 的密鑰不會被洩漏,我們將它存放在 .env 檔案中,這樣可以避免密鑰直接暴露在程式碼中。

在專案的根目錄創建一個 .env 檔案,並設定密鑰:

JWT_SECRET=yourSecretKeyHere

這樣你的密鑰就會存放在環境變數裡,而不是直接寫在程式碼中,這樣可以提高安全性。

步驟 3:在程式碼中讀取環境變數

接著,我們需要在程式入口檔案(如 app.jsserver.js)中引入 dotenv,來讀取環境變數中的密鑰。

require('dotenv').config();

這樣,我們就可以在程式碼中使用 process.env.JWT_SECRET 來讀取密鑰。

步驟 4:生成 JWT

當用戶成功登入時,後端會生成一個 JWT 並發送給前端。這裡展示的是一個基本的登入流程,當用戶驗證成功後,我們生成 JWT,並將它回傳給前端。

controllers/authController.js:

const jwt = require('jsonwebtoken');

// 登入成功後生成 JWT
exports.login = (req, res) => {
  const { username, password } = req.body;

  // 假設用戶驗證成功
  const userId = 1; // 這裡的 userId 是從資料庫中取得的用戶ID
  const token = jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '1h' }); // 生成 JWT

  res.json({ token });
};
  • jwt.sign():這個方法會生成一個 JWT。
  • { userId }:這是我們想要存進 Token 的數據。在這裡,我們把用戶的 ID 存進去,後續我們就可以通過這個 Token 確認用戶身份。
  • process.env.JWT_SECRET:這是從環境變數中讀取的密鑰,確保密鑰不會暴露在程式碼中。
  • { expiresIn: '1h' }:這個選項設定 Token 的有效期。在這裡,我們設定 Token 一小時後過期,這樣可以提高系統的安全性。

步驟 5:引入 authController 路由

你需要在 app.js 中引入並使用該路由。可以參考以下步驟:

修改 app.js

var authController = require('./controllers/authController'); // 確保引入 authController

app.post('/login', authController.login); // 為 /login 路徑設置 POST 路由

這樣可以讓 Express 知道 /login 的請求應該由 authController.login 處理。


4. 使用強密鑰來加強 JWT 安全性

為了確保 JWT 的安全性,我們需要使用足夠強度的密鑰。你可以使用 randomkeygen.com 來生成一個隨機密鑰,具體步驟如下:

  1. 前往 randomkeygen.com
  2. 選擇 "CodeIgniter Encryption Keys""256-bit WEP Keys"
  3. 選擇一個長度足夠的隨機密鑰,並將其複製到 .env 檔案中:
JWT_SECRET=dc18b603874e5e42d60dd9a9d712d8edfe017bce4792d3e676db9083ed1a6769

這樣,你的密鑰就足夠隨機且安全,能夠有效防範潛在的安全風險。

https://ithelp.ithome.com.tw/upload/images/20241003/20168326OH6IBhKwIW.png


5. 驗證 JWT 保護後端資源

在開發後端 API 的時候,怎麼確保只有登入過的用戶才能看到重要資料?這就是 JWT 的用武之地!我們可以用 JWT 來做身份驗證,確保那些敏感的 API 路由不會被隨便亂存取。要達成這個目的,我們會用到一個叫 中介層(Middleware) 的東西。

什麼是中介層(Middleware)?

中介層就像是 API 路由的守門員一樣,負責在請求到達路由之前,先檢查這個請求合不合法。它可以在請求進入具體路由前,先幫你做一些檢查動作。在我們的例子中,這個中介層會檢查用戶的請求中有沒有帶著有效的 JWT。如果沒有,那就直接拒絕!如果有,且通過驗證,就讓請求繼續下去。

為什麼要用 verifyToken 中介層?

這個中介層超重要,因為它幫我們把關 API 的安全性。很多時候,我們的 API 只允許登入的用戶存取,例如:查詢個人資料、刪除資料、更新設定等等。如果沒有 JWT 做驗證,那任何人都能隨便發送請求來修改資料,這樣就太危險了!

透過 verifyToken 中介層,我們可以確保只有那些帶著有效 JWT 的用戶才能存取這些 API,這樣不但提高了安全性,也讓 API 更具保護性。

中介層怎麼運作?

  1. 前端發送請求時帶上 JWT:用戶一旦成功登入,前端會拿到一個 JWT。之後每次發送 API 請求,前端會把這個 Token 放進 Authorization Header,告訴後端:「我有 Token,快讓我進去吧!」。
  2. 中介層驗證 JWT:在後端,我們的 verifyToken 中介層會讀取請求的 Header,檢查有沒有帶上 JWT。如果沒有,那就直接回應「403 禁止存取」。如果有帶 Token,就會用伺服器上的密鑰來檢查這個 Token 是否有效。
  3. 驗證通過後繼續請求:通過驗證後,中介層會把用戶的資料(例如 userId)附加到請求物件上,這樣後端後續的邏輯就可以用這個資訊來判斷這個請求是誰發的。

如何使用 verifyToken 中介層?

來看看我們如何在 middlewares/authMiddleware.js 中寫一個 verifyToken 函數:

const jwt = require('jsonwebtoken');

// 驗證 JWT Token 的中介層
exports.verifyToken = (req, res, next) => {
  const token = req.headers['authorization']; // 從請求的 Headers 中取出 JWT

  // 如果請求中沒有 Token,回應錯誤訊息
  if (!token) {
    return res.status(403).json({ message: 'Token is required' }); // 403:禁止存取
  }

  try {
    // 使用密鑰驗證 Token,成功後把解碼的資料加到請求物件中
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.userId = decoded.userId; // 把 userId 保存到請求物件中
    next(); // 通過驗證,繼續執行下一個中介層或路由處理程式
  } catch (err) {
    return res.status(401).json({ message: 'Invalid Token' }); // 401:身份驗證失敗
  }
};

這段程式在做什麼?

  • 檢查 Token 是否存在:它會檢查每個請求有沒有帶著 JWT。如果沒帶,就直接回應「403 Forbidden」,禁止存取。
  • 驗證 Token:如果請求中有帶 JWT,會使用伺服器的密鑰來驗證它。如果 JWT 是有效的,那就把用戶的 userId 加到請求物件上,這樣我們就知道這個請求是誰發的。
  • 驗證失敗:如果驗證失敗,像是 JWT 過期了或不正確,伺服器會回應「401 Unauthorized」,拒絕繼續執行。

什麼時候會呼叫 verifyToken

每當有 API 路由需要保護的時候,我們都會用到這個中介層。比如說,你有一個 API 是用來讓用戶查詢個人資料的,那我們就要確保這個路由只能被已登入的用戶存取。我們可以在這個路由中加上 verifyToken,這樣每次有人試圖存取這個 API,後端都會先檢查他的 Token。

舉例:用 verifyToken 保護路由

假設我們有一個 API 路由是 /api/products,這是只有已登入的用戶才能存取的。那麼我們可以這樣寫:

// routes/productRoutes.js
const express = require('express');
const router = express.Router();
const { verifyToken } = require('../middlewares/authMiddleware');

// 保護 /products 路由,只允許已登入用戶存取
router.get('/products', verifyToken, (req, res) => {
  res.json({ message: '這是產品資料,只有登入用戶才能看到!' });
});

module.exports = router;

這樣,當用戶試圖存取 /api/products 時,後端會先檢查他的 JWT。如果 Token 有效,就可以順利返回產品資料;如果無效,就會被擋在門外。

JWT 驗證的流程是怎樣的?

  1. 前端發送請求:用戶登入後,前端會把 JWT 放在請求的 Authorization Header 中,每次發送 API 請求都帶著這個 Token。

    const token = localStorage.getItem('token');
    const headers = { 'Authorization': `Bearer ${token}` };
    fetch('http://localhost:3000/api/products', { headers })
      .then(response => response.json())
      .then(data => console.log(data));
    
  2. 後端驗證 Token:後端的 verifyToken 中介層會讀取請求的 Authorization Header,檢查這個 Token 是否存在並且有效。如果驗證通過,就讓請求繼續執行;如果失敗,回應「401 Unauthorized」。

  3. API 請求流程:通過 JWT 驗證後,後端就會執行對應的邏輯,比如說返回資料給用戶。如果沒有通過驗證,後端會回應錯誤訊息,拒絕存取。


6. 使用 Postman 測試後端 JWT 身份驗證流程

既然我們的 JWT 實作好了,那接下來就要測試是否能正常運作。這裡我們會用 Postman 來模擬前端發送請求,並驗證我們的後端流程。

測試登入 API

  1. 打開 Postman,建立一個 POST 請求,URL 為 http://localhost:3000/login
  2. Body 區塊選擇 raw 格式,並設定為 JSON
  3. 在 Body 中輸入用戶名與密碼的假資料,例如:
{
  "username": "testuser",
  "password": "password"
}
  1. 點擊 Send 發送請求。如果一切正常,應該會收到類似這樣的回應:
{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

這個回應中的 token 就是 JWT,之後的每次請求,我們都會帶著這個 Token。

https://ithelp.ithome.com.tw/upload/images/20241003/20168326T5BcYaLtAS.png


7. 用 JWT 打造你的專屬身份驗證盾牌

JWT 是一個簡單但功能強大的工具,能夠幫助我們實現安全高效的身份驗證。在實作 JWT 時,從生成 Token 到驗證 Token,整個流程非常直觀,但仍然需要注意安全細節。將密鑰儲存在環境變數中,並使用強隨機密鑰來加強安全性,是我們應該遵循的基本安全措施。

在本文中,我們介紹了如何在後端 Node.js(Express) 中實作 JWT,包括生成和驗證 Token 的具體步驟,還提供了如何保護後端資源的範例。透過 JWT,你可以有效地管理使用者的登入狀態和權限,讓整個系統的安全性更上層樓。

接下來,會在下一篇文章中介紹如何在 Angular 前端 使用這個 JWT,讓前後端無縫整合,實現完整的身份驗證流程。


上一篇
Day24 F12 開發者工具超實用功能,前端後端調整都不再辛苦!
下一篇
Day26 登入功能初體驗,JWT 的身份驗證流程!(下)
系列文
從零開始:全端新手的困境與成長30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言